#pragma once

#include "device.hpp"
#include <functional>
#include <memory>
#include <mutex>
#include <vector>

class DeviceManager {
private:
    using Action = std::function<void(std::shared_ptr<Device>)>;
    using AddDeviceAction = std::vector<Action>;
    using DelDeviceAction = AddDeviceAction;

private:
    std::mutex _mutex;
    std::vector<std::shared_ptr<Device>> _deviceList;

    AddDeviceAction _addDeviceAction;
    DelDeviceAction _delDeviceAction;

private:
    std::shared_ptr<Device> _findDevice(std::size_t id) {
        auto it = std::find_if(_deviceList.begin(), _deviceList.end(), [id](std::shared_ptr<Device> p) { return p->id() == id; });
        if (it == _deviceList.end()) {
            return nullptr;
        }

        return *it;
    }

public:
    std::shared_ptr<Device> findDevice(std::size_t id) {
        std::lock_guard<std::mutex> _lock(_mutex);

        auto it = std::find_if(_deviceList.begin(), _deviceList.end(), [id](std::shared_ptr<Device> p) { return p->id() == id; });
        if (it == _deviceList.end()) {
            return nullptr;
        }

        return *it;
    }

    void addDevice(const Device &d) {
        std::lock_guard<std::mutex> _lock(_mutex);

        if (_findDevice(d.id())) {
            std::cerr << "device already exist, please check" << std::endl;
            return;
        }

        _deviceList.emplace_back(std::make_shared<Device>(d));

        std::for_each(_addDeviceAction.begin(), _addDeviceAction.end(), [this, &d](const auto &f) { f(_findDevice(d.id())); });
    }

    void delDevice(std::size_t id) {
        std::lock_guard<std::mutex> _lock(_mutex);
        std::for_each(_delDeviceAction.begin(), _delDeviceAction.end(), [this, &id](const auto &f) { f(_findDevice(id)); });
        _deviceList.erase(std::remove_if(_deviceList.begin(), _deviceList.end(), [id](const auto &d) { return d->id() == id; }), _deviceList.end());
    }

    void delAllDevice(void) {
        std::lock_guard<std::mutex> _lock(_mutex);

        for (auto &it : _deviceList) {
            std::for_each(_delDeviceAction.begin(), _delDeviceAction.end(), [&it](const auto &f) { f(it); });
        }

        _deviceList.clear();
    }

    template <typename F, typename... Args>
    DeviceManager &registerOnAddDevice(F &&f, Args &&... args) {
        _addDeviceAction.emplace_back(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
        return *this;
    }

    template <typename F, typename... Args>
    DeviceManager &registerOnDelDevice(F &&f, Args &&... args) {
        _delDeviceAction.emplace_back(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
        return *this;
    }
};
